﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace NDI.Tests
{
    
    [TestClass]
    public class ContainerFixture
    {
        [TestMethod]
        public void CreatingInstance()
        {
            var bar = new Bar();
            var foo = new Foo(bar);

            Func<Bar> barFactory = () => new Bar();
            Func<Foo> fooFactory = () => new Foo(barFactory.Invoke());
        }

        [TestMethod]
        public void RegistersTypeAndGetInstance()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar());

            var container = builder.Build();
            var bar = container.Resolve<IBar>();

            Assert.IsNotNull(bar);
            Assert.IsTrue(bar is Bar);
        }

        [TestMethod]
        public void ResolveGetsDependenciesInjected()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar());
            builder.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));

            var container = builder.Build();
            var foo = container.Resolve<IFoo>() as Foo;

            Assert.IsNotNull(foo);
            Assert.IsNotNull(foo.Bar);
            Assert.IsTrue(foo is Foo);
        }

        [TestMethod]
        public void ConstructorArgumentPassedOnResolve()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar, string>((c, s) => new Bar(s));

            var container = builder.Build();
            var bar = container.Resolve<IBar, string>("foo") as Bar;
            //这个时候只是在Container中注册了获取Bar对象的方法，但是真正获取是在Resolve方法时，并将相应的属性赋值进去了
            Assert.AreEqual("foo", bar.Arg1);
        }

        [TestMethod]
        public void ConstructorArgumentsPassedOnResolve()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar, string, bool>((c, s, b) => new Bar(s, b));

            var container = builder.Build();
            var bar = container.Resolve<IBar, string, bool>("foo", true) as Bar;

            Assert.AreEqual("foo", bar.Arg1);
            Assert.AreEqual(true, bar.Arg2);
        }

        [TestMethod]
        public void RegisterMultiConstructorOverloads()
        {
            //如果只通过ServiceType来作为Container的键的话，如果同时注册了多个此类型的Service便会出现只有一个键值对应多个对象的情况
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar());
            builder.Register<IBar, string>((c, s) => new Bar(s));

            var container = builder.Build();
            var bar = container.Resolve<IBar, string>("foo") as Bar;
            var bar2 = container.Resolve<IBar>();

            Assert.IsNotNull(bar);
            Assert.IsNotNull(bar2);
            Assert.AreEqual("foo", bar.Arg1);
        }

        //如果ServiceTye 和 FactoryType都一样，便又会重复，所以要用名字来区分
        [TestMethod]
        public void ResolveNamedService()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar("bar1")).Named("a");
            builder.Register<IBar>(c => new Bar("bar2")).Named("b");
            
            var container = builder.Build();
            var bar1 = container.ResolveNamed<IBar>("a") as Bar;
            var bar2 = container.ResolveNamed<IBar>("b") as Bar;

            Assert.IsNotNull(bar1);
            Assert.IsNotNull(bar2);
            Assert.AreEqual("bar1", bar1.Arg1);
            Assert.AreEqual("bar2", bar2.Arg1);
        }

        //如果ServiceTye 和 FactoryType都一样，便又会重复，所以要用名字来区分
        [TestMethod]
        public void RegistrationDoesNotMattter()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar("noname"));//如果把注册放在前面就会与后面的冲突。
            builder.Register<IBar>(c => new Bar("bar1")).Named("a");
            builder.Register<IBar>(c => new Bar("bar2")).Named("b");

            var container = builder.Build();
            var bar1 = container.ResolveNamed<IBar>("a") as Bar;
            var bar2 = container.ResolveNamed<IBar>("b") as Bar;
            var bar3 = container.Resolve<IBar>() as Bar;

            Assert.IsNotNull(bar1);
            Assert.IsNotNull(bar2);
            Assert.AreEqual("bar1", bar1.Arg1);
            Assert.AreEqual("bar2", bar2.Arg1);
            Assert.AreEqual("noname", bar3.Arg1);
        }

        [TestMethod]
        public void ResolveNotRegisteredServiceThrows()
        {
            var builder = new ContainerBuilder();
            var container = builder.Build();
            try
            {
                var foo = container.Resolve<IFoo>();
                Assert.Fail("Should throw a new NDIException");
            }
            catch (NDIException)
            {
            	
            }
        }

        [TestMethod]
        public void TryResolveNotRegisteredServicesReturnNull()
        {
            var builder = new ContainerBuilder();
            var container = builder.Build();
            var bar = container.TryResolve<IBar>();
            Assert.IsNull(bar);
        }

        [TestMethod]
        public void ConstructorArgumentPassedOnTryResolve()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar, string>((c, s) => new Bar(s));

            var container = builder.Build();
            var bar = container.TryResolve<IBar, string>("foo") as Bar;
            //这个时候只是在Container中注册了获取Bar对象的方法，但是真正获取是在Resolve方法时，并将相应的属性赋值进去了
            Assert.AreEqual("foo", bar.Arg1);
        }

        [TestMethod]
        public void ConstructorArgumentsPassedOnTryResolve()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar, string, bool>((c, s, b) => new Bar(s, b));

            var container = builder.Build();
            var bar = container.TryResolve<IBar, string, bool>("foo", true) as Bar;

            Assert.AreEqual("foo", bar.Arg1);
            Assert.AreEqual(true, bar.Arg2);
        }

        [TestMethod]
        public void InstanceReusedWithinContainer()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar()).ReuseWithin(ReuseScope.Container);

            var container = builder.Build();
            var bar1 = container.Resolve<IBar>();
            var bar2 = container.Resolve<IBar>();

            Assert.AreSame(bar1, bar2);
        }

        [TestMethod]
        public void InstanceReuseNotWithinContainer()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c=>new Bar()).ReuseWithin(ReuseScope.None);
            
            var container = builder.Build();
            var bar1 = container.Resolve<IBar>();
            var bar2 = container.Resolve<IBar>();

            Assert.AreNotSame(bar1, bar2);
        }

        [TestMethod]
        public void RegisterOnParentAndResolveOnChildContainer()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new Foo());

            var container = builder.Build();
            var childContainer = container.CreatChildContainer();//目前来看只是对父Container做了一份拷贝
            var foo = childContainer.Resolve<IFoo>();

            Assert.IsNotNull(foo);
        }

        [TestMethod]
        public void HierarchScopeInstanceReusedOnSameContainer()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new Foo()).ReuseWithin(ReuseScope.Hierarchy);

            var container = builder.Build();
            var foo1 = container.Resolve<IFoo>();
            var foo2 = container.Resolve<IFoo>();

            Assert.AreSame(foo1, foo2);
        }

        [TestMethod]
        public void HierarchScopeInstanceReusedOnChildContainer()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new Foo()).ReuseWithin(ReuseScope.Hierarchy);

            var parent = builder.Build();
            var childContainer = parent.CreatChildContainer();

            var foo1 = parent.Resolve<IFoo>();
            var foo2 = childContainer.Resolve<IFoo>();

            Assert.AreSame(foo1, foo2);
        }

        [TestMethod]
        public void ContainerScopeInstanceNotReusedOnChildContainer()
        {
            //如果是在container级别的reuse，那么childContainer不能与parentContainer共享注入的实体
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new Foo()).ReuseWithin(ReuseScope.Container);

            var parent = builder.Build();
            var childContainer = parent.CreatChildContainer();

            var foo1 = parent.Resolve<IFoo>();
            var foo2 = childContainer.Resolve<IFoo>();

            Assert.AreNotSame(foo1, foo2);
        }

        [TestMethod]
        public void InstancesDisposedByContainerDisposedReusedWithinScopeNone()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.None).OwnedBy(Owner.Container);

            var container = builder.Build();
            var foo = container.Resolve<IFoo>() as DisposableFoo;

            container.Dispose();

            Assert.IsTrue(foo.IsDisposed);
        }

        [TestMethod]
        public void InstanceKeepNotAliveReusedWithinScopeNone()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.None).OwnedBy(Owner.Container);

            var container = builder.Build();
            var foo = container.Resolve<IFoo>() as DisposableFoo;

            WeakReference wr = new WeakReference(foo);

            foo = null;
            GC.Collect();

            Assert.IsFalse(wr.IsAlive);
        }

        [TestMethod]
        public void ContainerOwnedAndContainerReusedInstancesAreDisposed()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.Container).OwnedBy(Owner.Container);

            var container = builder.Build();
            var foo = container.Resolve<IFoo>() as DisposableFoo;

            container.Dispose();

            Assert.IsTrue(foo.IsDisposed);
        }

        [TestMethod]
        public void ContainerOwnedAndHierarchyReusedInstancesAreDisposed()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.Hierarchy).OwnedBy(Owner.Container);

            var parent = builder.Build();
            var child = parent.CreatChildContainer();

            var childFoo = child.Resolve<IFoo>() as DisposableFoo;

            child.Dispose();

            Assert.IsFalse(childFoo.IsDisposed);
        }

        [TestMethod]
        public void DisposeParentContainerAndDisposeChildContainerInstances()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.Container).OwnedBy(Owner.Container);

            var parent = builder.Build();
            var child = parent.CreatChildContainer();

            var childFoo = child.Resolve<IFoo>() as DisposableFoo;

            parent.Dispose();

            Assert.IsTrue(childFoo.IsDisposed);
        }

        [TestMethod]
        public void DisposeContainerDoesnotDisposeExternalInstances()
        {
            var builder = new ContainerBuilder();
            builder.Register<IFoo>(c => new DisposableFoo()).ReuseWithin(ReuseScope.Container).OwnedBy(Owner.External);//当owned by external 时，就不会放入到释放池中

            var container = builder.Build();
            var foo = container.Resolve<IFoo>() as DisposableFoo;

            container.Dispose();

            Assert.IsTrue(foo.IsDisposed);
        }

        [TestMethod]
        public void RepeatBuildGetSameContainer()
        {
            var builder = new ContainerBuilder();

            builder.Register<IBar>(c=>new Bar()).ReuseWithin(ReuseScope.Container).OwnedBy(Owner.Container);
            var container = builder.Build();

            var bar = container.Resolve<IBar>();
            builder.Register<IFoo>(c => new Foo(bar)).ReuseWithin(ReuseScope.Container).OwnedBy(Owner.Container);
            var container1 = builder.Build();
            var foo = container.Resolve<IFoo>();

            Assert.IsNotNull(bar);
            Assert.IsNotNull(foo);
            Assert.AreSame(container, container1);
        }

        [TestMethod]
        public void BuildFlutUI()
        {
            //give the developer the api level(限制),advoid error call
            var builder = new ContainerBuilder();

            builder.Register<IFoo>(c => new DisposableFoo())
                .Named("aa")
                .ReuseWithin(ReuseScope.None)
                .OwnedBy(Owner.External);

            var container = builder.Build();
            var fool = container.ResolveNamed<IFoo>("aa") as DisposableFoo;

            container.Dispose();
            Assert.IsTrue(fool.IsDisposed);
        }

        public class DisposableFoo: IDisposable,IFoo
        {
            public bool IsDisposed { get; set; }

            public void Dispose()
            {
                this.IsDisposed = true;
            }
        }


        public interface IBar { }
        public interface IFoo { }

        public class Bar : IBar
        {
            public Bar()
            {

            }

            public Bar(string arg1)
            {
                this.Arg1 = arg1;
            }

            public Bar(string arg1,bool arg2)
            {
                this.Arg1 = arg1;
                this.Arg2 = arg2;
            }

            public string Arg1 { get; private set; }
            public bool Arg2 { get; private set; }
        }

        public class Foo : IFoo
        {
            public IBar Bar { get; private set; }

            public Foo()
            {

            }

            public Foo(IBar bar)
            {
                this.Bar = bar;
            }
        }

    }
}
